grammar YAGI;

options {
  language = Java;
  backtrack = true;
}

@lexer::header {package org.yagi;}

@header {
  package org.yagi;
  
	import alice.tuprolog.Prolog;
	import alice.tuprolog.SolveInfo;
	import alice.tuprolog.MalformedGoalException;
	import alice.tuprolog.NoSolutionException;
	import java.util.HashMap;
	import java.util.Map;
	import java.util.HashSet;
	import java.util.Set;
	import java.util.ArrayList;
	import java.util.List;
	import java.util.logging.Level;
	import java.util.logging.Logger;
}

@members {
	Map<String,Integer> fluents, actions;
	HashSet<String> localvars,argvars;
	int prologVars = 0;
	Prolog engine;
	Logger LOGGER;
	
 	public YAGIParser(Map fluents, Map actions, Prolog engine, TokenStream input, Logger LOGGER) 
 		throws Exception
 	{
        this(input, new RecognizerSharedState());
        this.fluents = fluents;
        this.actions = actions;
        this.engine = engine;
        this.LOGGER = LOGGER;
        localvars = new HashSet<String>();
        argvars = new HashSet<String>();
    }
    
    String getNewPrologVar(){
    	prologVars++;
    	return "Y" + Integer.toString(prologVars);
    }
    
    String genFreeVars(int argnum){
		String s = "";
		int i;
		for (i = 0; i< argnum - 1 ; i++)
			s += "X"+ Integer.toString(i) + ",";
		s += "X" + Integer.toString(i);
		return s;
    }
}

// main

line returns [String ret, String pcode]
	:	declaration             {$ret = $declaration.ret;}
	| 	block {    
	           try { 
	              $pcode = "do(" + $block.pcode + ", s0, SS), unwind(SS,S).";
                LOGGER.log(Level.FINER, "Prolog code to be executed (Golog block): " + $pcode);
	              SolveInfo info = engine.solve($pcode); 
                $ret = info.getTerm("S").toString();
              } catch (NoSolutionException e){
                $ret = "[false]";
              } catch (Exception e){
                LOGGER.log(Level.SEVERE, "Exception " + e);
                
              }           
              LOGGER.log(Level.FINER, "Prolog S:" + $ret);
            }
  |   term  {
             Integer v = fluents.get($term.id);
             if (v==null){
                $ret = "[Fluent " + $term.id.substring(1) + " undefined]";
             }
             else{
              try { 
                $pcode = "restoreSitArg(" + $term.pcode + ",s0,X), findall(" + $term.pvar + ", X, L).";
                LOGGER.log(Level.FINER, "Prolog code to be executed (Golog term): " + $pcode);
                SolveInfo info = engine.solve($pcode); 
                $ret = info.getTerm("L").toString();
                $ret = "{" + $ret.substring(1,$ret.length()-1) + "}";
              } catch (NoSolutionException e){
                $ret = "[false]";
              } catch (Exception e){
                LOGGER.log(Level.SEVERE, "Exception " + e);
                
              }           
              LOGGER.log(Level.FINER, "Prolog S:" + $ret);
             }
            }
	;


declaration returns [String ret]
	:	fluent_decl ';'
	|	fact_decl ';'
	|	action_decl
	| proc_decl
	|	assignment              {$ret = $assignment.ret;}
	;
	

// proc declaration

proc_decl
	:	'proc' ID '(' varlist ')' block 'end proc'
	{LOGGER.log(Level.WARNING, "Proc declaration is not supported in this version, declaration is ignored.");}  
	;

// extended statemens (golog)

block returns [String pcode]
	:	s=statement             {$pcode = $s.pcode;} 
	  (s=statement            {$pcode += " : " + $s.pcode;} )*
	;

statement returns [String pcode]
	:	action_exec ';'         {$pcode = $action_exec.pcode;}
	|	pick                    {$pcode = $pick.pcode;}
	|	test ';'                {$pcode = $test.pcode;}
	|	for_loop
	|	if_then_else
	|	choose                  {$pcode = $choose.pcode;}
	;

action_exec returns [String pcode]
	:	ID '(' ')'              {$pcode = $ID.text;}
  | ID '(' arglist ')'      {$pcode = $ID.text + "(" + $arglist.pcode + ")";}
	;
	
arglist returns [String pcode]
	:	
	|	v=value                 {$pcode = $v.d;} 
	  ( ',' v=value           {$pcode += "," + $v.d;}
	  )*
	;

pick returns [String pcode]	
  :	'pick' var 'from' setexpr 'such' block 'end pick'
    {$pcode = "pi(" + $var.id + ", (?( yeq(" + $setexpr.pvar + "," + $var.id + ")" 
      + " & " + $setexpr.pcode + ")) : " + $block.pcode + ")";
     LOGGER.log(Level.FINER, "pick parsed, pcode: " + $pcode);
    }
 	;

test returns [String pcode]	
  :	'test' formula          {$pcode = "?(" + $formula.pcode + ")";}
	;
	
for_loop:	'for' var 'in' setexpr 'do' block 'end for'
  {LOGGER.log(Level.WARNING, "(Bounded loops not supported in this version.");}  
	;	

if_then_else
	:	'if' formula 'then' block ('else' block)? 'end if'
  {LOGGER.log(Level.WARNING, "if-then-else not supported in this version, please use ?() and ( : ) instead.");}  
	;

choose returns [String pcode]
  : 'choose' b=block          {$pcode = $b.pcode;}  
    ( 'or' b=block            {$pcode = "(" + $pcode + "#" + $b.pcode + ")";}
    )+ 'end choose'
	;


	
// action declaration

action_decl
	:	'action' ID '(' varlist ')'
		('precondition:' formula)?
		('effect:' assignment)?
		('signal:' valexpr)?
		'end action'
  {LOGGER.log(Level.WARNING, "Action declaration is not supported in this version, declaration is ignored.");}  
	;

varlist
	:	
	|	var ( ',' var )*
	;


// statements


assignment returns [String ret]
	:	assign ';'         {$ret = $assign.ret;}
	|	for_loop_assign
	|	conditional_assign
	;

for_loop_assign
	:	'for' var 'in' setexpr 'do' assignment 'end for'
  {LOGGER.log(Level.WARNING, "For-loop assignment is not supported in this version, statement is ignored.");}  
	;
	
conditional_assign
	:	'if' formula 'then' assignment ('else' assignment)? 'end if'
	;

assign returns [String pcode, String ret] //***** more members needed to hold info for action declaration 
	:	term  '=' setexpr
					{
						 Integer v = fluents.get($term.id);
             if (v==null){
                $ret = "[Fluent " + $term.id.substring(1) + " undefined]";
             }
             else{
              try { 
                $pcode = "restoreSitArg("+ $term.pcode +",s0,X), retractall(X).";
                LOGGER.log(Level.FINER, "Prolog code to be executed (Golog assign): " + $pcode);
                SolveInfo info = engine.solve($pcode); 
                $pcode = "findall(" + $setexpr.pvar + ", "  
                          + "holds(" + $setexpr.pcode + " & " + "yeq(" + $setexpr.pvar + "," + $term.pvar + ") & "
                          + "restoreSitArg(" + $term.pcode + ", s0, X)" + " & " + "assert(X), s0), L).";
        //        $pcode = "findall(" + $setexpr.pvar + ", (" + $setexpr.pcode + "," + $setexpr.pvar + "=" + $term.pvar 
        //                  + ",restoreSitArg("+ $term.pcode +",s0,X), assert(X)), L).";
                LOGGER.log(Level.FINER, "Prolog code to be executed (Golog assign): " + $pcode);
                info = engine.solve($pcode); 
                $ret = " ";
              } catch (Exception e){
                LOGGER.log(Level.SEVERE, "Exception " + e);
                
              }           
              //LOGGER.log(Level.FINER, "Prolog S:" + $ret);
             }	
				   }
	|	term '+=' setexpr
          {
             Integer v = fluents.get($term.id);
             if (v==null){
                $ret = "[Fluent " + $term.id.substring(1) + " undefined]";
             }
             else{
              try { 
                $pcode = "findall(" + $setexpr.pvar + ", "  
                          + "holds(" + $setexpr.pcode + " & " + "yeq(" + $setexpr.pvar + "," + $term.pvar + ") & "
                          + "restoreSitArg(" + $term.pcode + ", s0, X)" + " & " + "assert(X), s0), L).";
                LOGGER.log(Level.FINER, "Prolog code to be executed (Golog assign): " + $pcode);
                SolveInfo info = engine.solve($pcode); 
                $ret = " ";
              } catch (Exception e){
                LOGGER.log(Level.SEVERE, "Exception " + e);
                
              }           
              //LOGGER.log(Level.FINER, "Prolog S:" + $ret);
             }  
           }	
  |	term '-=' setexpr
          {
             Integer v = fluents.get($term.id);
             if (v==null){
                $ret = "[Fluent " + $term.id.substring(1) + " undefined]";
             }
             else{
              try { 
                $pcode = "findall(" + $setexpr.pvar + ", "  
                          + "holds(" + $setexpr.pcode + " & " + "yeq(" + $setexpr.pvar + "," + $term.pvar + ") & "
                          + "restoreSitArg(" + $term.pcode + ", s0, X)" + " & " + "retract(X), s0), L).";
                LOGGER.log(Level.FINER, "Prolog code to be executed (Golog assign): " + $pcode);
                SolveInfo info = engine.solve($pcode); 
                $ret = " ";
              } catch (Exception e){
                LOGGER.log(Level.SEVERE, "Exception " + e);
                
              }           
              //LOGGER.log(Level.FINER, "Prolog S:" + $ret);
             }  
           }
  | var '=' valexpr
  | var '+=' valexpr
  | var '-=' valexpr
  ;


/*
assign	
	:	term assign_op setexpr
	|	var assign_op valexpr
	;
	
assign_op
	:	'='
	| 	'+='
	| 	'-='
	;
*/

// formulas

formula returns [String pcode]	
	:	a=atom				{$pcode = $a.pcode;}
	|	'not' '(' b=formula ')'
							{$pcode = "-(" + $b.pcode + ")";}
	|	'(' a=atom c=connective b=formula ')'
							{$pcode = "(" + $a.pcode + $c.op + $b.pcode + ")";}
	|	'exists' v=var 'in' s=setexpr 'such' b=formula
							{$pcode = "some(" + $v.id + ", (( yeq(" + $s.pvar + "," + $v.id + ")" + " & " + $s.pcode + ") & " + $b.pcode + "))";}
	|	'all' v=var 'in' s=setexpr 'such' b=formula
							{$pcode = "all(" + $v.id + ", (( yeq(" + $s.pvar + ","+ $v.id + ")" + " & " + $s.pcode + ") => " + $b.pcode + "))";}
	;
	
connective returns [String op]
	:	'and'				{$op = " & ";}
	|	'or'				{$op = " v ";}
	;	

atom returns [String pcode]
	:	e1=valexpr c=comp_op e2=valexpr
							{$pcode = "(" + $e1.d + $c.op + $e2.d + ")";
							  LOGGER.log(Level.FINER, "Token atom parsed, pcode: " + $pcode);
							}
	|	setexpr comp_op setexpr
							{LOGGER.log(Level.WARNING, "Comparison between set expressions is not supported in this version, the corresponding atom is ignored.");}
	|	b1=value 'in' b2=setexpr	
							{$pcode = "( yeq(" + $b1.d + "," + $b2.pvar + ")" + " & " + $b2.pcode + ")";
							 LOGGER.log(Level.FINER, "Token atom parsed, pcode: " + $pcode);
							}
	|	'true'				{$pcode = "true";}
	| 	'false'				{$pcode = "false";}
	;
	
comp_op returns [String op]
	:	'=='				{$op = "=";}
	|	'!='				{$op = "\\=";}
	|	'<='				{$op = "=<";}
	|	'>='				{$op = ">=";}	
	|	'<'					{$op = "<";}
	|	'>'					{$op = ">";}
	;

//	
//  expressions
//

setexpr	returns [List<String> elems, String pvar, String pcode, Boolean simple]	
	:	e=set				{$elems = $e.elems; $simple=true; $pvar = $e.pvar; $pcode = $e.pcode;} 
		(('+'|'-') set		{$simple = false;}
		)*					{if (!$simple) LOGGER.log(Level.WARNING, "Arithmetic not supported in this version, only the first value of the expression is considered.");
							 LOGGER.log(Level.FINER, "Token setexpr parsed, pcode: " + $pcode + " pvar: " + $pvar);
							}
	;

set returns [List<String> elems, String pvar, String pcode]
	:	'{' a=value		{$elems = new ArrayList<String>(); $elems.add($a.d); 
						 $pvar = getNewPrologVar();
						 $pcode = "(yeq(" + $pvar + "," + $a.d + ")";
						} 
		( ',' b=value 	{$elems.add($b.d); 
						 $pcode += " v " + "yeq(" + $pvar + "," + $b.d + ")";
						}
		)* '}'			{$pcode += ")";}
	|	e=term			{$pvar = $e.pvar; $pcode = $e.pcode;
						 LOGGER.log(Level.FINER, "Token set parsed, pcode: " + $pcode + " pvar: " + $pvar);	
						}
	;

term returns [String id, List<String> args, String pvar, String pcode]
 	:	ID 				{$id = "f" + $ID.text; $args = new ArrayList<String>();}
 		( '[' value ']' {$args.add($value.d);}
 		)*				{$pcode = $id + "(";
 						 for (int i=0; i<$args.size(); i++)
 							$pcode += $args.get(i) + ",";
 						 $pvar = getNewPrologVar();
 						 $pcode += $pvar + ")";
						 LOGGER.log(Level.FINER, "Token term parsed, pcode: " + $pcode + " pvar: " + $pvar);
 						}
	;

valexpr returns [String d, Boolean simple]	
	:	e=value 		{$d = $e.d; $simple=true;} 
		(('+'|'-') value{$simple = false;}
		)*				{if (!$simple) LOGGER.log(Level.WARNING, "Arithmetic not supported in this version, only the first value of the expression is considered.");}
	;

value returns [String d]
	:	INT		{$d = $INT.text;}
	|	STRING	{$d = $STRING.text;}
	|	var		{$d = $var.id;}
	;

var returns [String id]
	:	'$' ID	{if (argvars.contains($ID.text)){ 
				 	$id = "ArgVar"+$ID.text;
				 }else {
				 	$id = "locVar"+$ID.text;
				 }
				}
	;

	
// fluent/fact and var declaration

fluent_decl returns [String pcode, String id, int argnum]
	:	'fluent' ID {$id = $ID.text; $argnum = 0;} 
		('[' ']' 	{$argnum++;})*
					{String freevars;
					 Integer v = fluents.get($id);
					 if (v==null){
					 	// Add the fluent information to the Map 
					 	fluents.put($id,new Integer($argnum));
					 	try { 
						 	// Provide the clause for restoring the situation argument
						 	freevars = genFreeVars($argnum+1);
						 	$pcode = "assert(restoreSitArg(" + "f" + $id + "(" + freevars + "),S," + "f" + $id + "(" + freevars + ",S))).";
					 		engine.solve($pcode); 
						 	LOGGER.log(Level.FINER, "Prolog code executed: " + $pcode);
					 	}catch (MalformedGoalException exc){
					 		LOGGER.log(Level.SEVERE, "MalformedGoalException " + exc);
					 	}					 	
					 	LOGGER.log(Level.FINE, "fluent " + $id + " initialized.");
					 }else{
						try { 
							// First retract all existing atoms of the fluent
					 		freevars = genFreeVars(v+1);
					 		$pcode = "retractall(" + "f" + $id + "(" + freevars + ")).";
					 		engine.solve($pcode); 
						 	LOGGER.log(Level.FINER, "Prolog code executed: " + $pcode);
					 		// Then retract the clause for restoring the situation argument
					 		$pcode = "retract(restoreSitArg(" + "f" + $id + "(" + freevars + "),S," + "f" + $id + "(" + freevars + ",S))).";
					 		engine.solve($pcode); 
						 	LOGGER.log(Level.FINER, "Prolog code executed: " + $pcode);
						 	// Then assert the new clause for restoring the situation argument
						 	freevars = genFreeVars($argnum+1);					 	
						 	$pcode = "assert(restoreSitArg(" + "f"+ $id + "(" + freevars + "),S," + "f" + $id + "(" + freevars + ",S))).";					 	
					 		engine.solve($pcode); 
						 	LOGGER.log(Level.FINER, "Prolog code executed: " + $pcode);
					 	}catch (MalformedGoalException exc){
					 		LOGGER.log(Level.SEVERE, "MalformedGoalException " + exc);
					 	}
					 	// Finally, add the fluent information to the Map 
					 	fluents.put($id,new Integer($argnum));
					 	// Log the event
					 	LOGGER.log(Level.FINE, "fluent " + $id + " re-initialized (old values have been discarded).");
					 }}
	;

fact_decl
:	'fact' ID ('[' ']')*  {LOGGER.log(Level.WARNING, "Facts not supported in this version, please use normal fluents instead.");}
	;

// Skipped because it is not included in the paper
//var_decl returns [String id]
//	:	'local' '$' ID {$id = $ID.text;} 
//	;


// lexicon

WHITESPACE : ( '\t' | ' ' | '\r' | '\n'| '\u000C' )+    { $channel = HIDDEN; } ;

ID  :	('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
    ;

INT :	'0'..'9'+
    ;

STRING
    :  '"' ( ~('\\'|'"') )* '"'
    ;

COMMENT
    :   '//' ~('\n'|'\r')* '\r'? '\n' {$channel=HIDDEN;}
    |   '/*' ( options {greedy=false;} : . )* '*/' {$channel=HIDDEN;}
    ;